#include "GenerateX86Code.h"
#include "Common.h"
#include <stdio.h>

#define IS_BETWEEN(VAL, START, END) ((VAL) >= (START) && (VAL) <= (END))

// mod - [0,3] (2 bits)
// reg - [0,7] (3 bits)
// rm  - [0,7] (3 bits)
#define MAKE_MOD_RM_BYTE(mod, reg, rm) (((UInt8)(rm)) | (((UInt8)(reg)) << 3) | (((UInt8)(mod)) << 6))

UInt GenerateX86Code(UInt8* out)
{
	struct Instruction* i = intermediateCode;
	UInt codeSize = 0;
	UInt instructionOffsets;

	while (TRUE)
	{
		switch (i->type)
		{
		case OT_CMP_REGISTER_AND_CONSTANT:
			{
				const enum REGISTER reg = ((struct CmpRegisterAndConstant*)(i + 1))->reg;
				const Int constant = ((struct CmpRegisterAndConstant*)(i + 1))->constant;

				switch (reg)
				{
				case R_AL:
					*out++ = 0x3C;
					*((Int8*)out)++ = (Int8)constant;
					codeSize += (sizeof(UInt8) + sizeof(Int8));
					break;
				case R_AX:
					*out++ = 0x66;
					*out++ = 0x3D;
					*((Int16*)out)++ = (Int16)constant;
					codeSize += (sizeof(UInt8)*2 + sizeof(Int16));
					break;
				case R_EAX:
					*out++ = 0x3D;
					*((Int32*)out)++ = (Int32)constant;
					codeSize += (sizeof(UInt8) + sizeof(Int32));
					break;
				case R_ECX:
				case R_EDX:
				case R_EBX:
				case R_ESP:
				case R_EBP:
				case R_ESI:
				case R_EDI:
					*out++ = 0x81;
					*out++ = MAKE_MOD_RM_BYTE(3, 7, reg);
					*((Int32*)out)++ = constant;
					codeSize += (sizeof(UInt8)*2 + sizeof(Int32));
					break;

				case R_CX:
				case R_DX:
				case R_BX:
				case R_SP:
				case R_BP:
				case R_SI:
				case R_DI:
					*out++ = 0x66;
					*out++ = 0x81;
					*out++ = MAKE_MOD_RM_BYTE(3, 7, reg);
					*((Int16*)out)++ = constant;
					codeSize += (sizeof(UInt8)*3 + sizeof(Int16));
					break;

				case R_CL:
				case R_DL:
				case R_BL:
				case R_AH:
				case R_CH:
				case R_DH:
				case R_BH:
					*out++ = 0x80;
					*out++ = MAKE_MOD_RM_BYTE(3, 7, reg);
					*((Int8*)out)++ = constant;
					codeSize += (sizeof(UInt8)*2 + sizeof(Int8));
					break;
				}

				(UInt8*)i += (sizeof(struct Instruction) + sizeof(struct CmpRegisterAndConstant));
				break;
			}
		case OT_MOV_CONSTANT_TO_REGISTER:
			{
				const enum REGISTER reg = ((struct MovConstantToRegister*)(i + 1))->dest;
				const Int constant = ((struct MovConstantToRegister*)(i + 1))->src;

				switch (reg)
				{
				case R_EAX:
				case R_ECX:
				case R_EDX:
				case R_EBX:
				case R_ESP:
				case R_EBP:
				case R_ESI:
				case R_EDI:
					*out++ = 0xB8 + reg;
					*((Int32*)out)++ = constant;
					codeSize += (sizeof(UInt8) + sizeof(Int32));
					break;

				case R_AX:
				case R_CX:
				case R_DX:
				case R_BX:
				case R_SP:
				case R_BP:
				case R_SI:
				case R_DI:
					*out++ = 0x66;
					*out++ = 0xB8 + (reg - R_AX);
					*((Int16*)out)++ = (Int16)constant;
					codeSize += (sizeof(UInt8)*2 + sizeof(Int16));
					break;

				case R_AL:
				case R_CL:
				case R_DL:
				case R_BL:
				case R_AH:
				case R_CH:
				case R_DH:
				case R_BH:
					*out++ = 0xB0 + (reg - R_AL);
					*((Int8*)out)++ = (Int8)constant;
					codeSize += (sizeof(UInt8) + sizeof(Int8));
					break;
				}

				(UInt8*)i += (sizeof(struct Instruction) + sizeof(struct MovConstantToRegister));
				break;
			}
		case OT_ADD_CONSTANT_TO_REGISTER:
			{
				const enum REGISTER reg = ((struct AddConstantToRegister*)(i + 1))->dest;
				const Int constant = ((struct AddConstantToRegister*)(i + 1))->src;

				switch (reg)
				{
				case R_AL:
					*out++ = 0x04;
					*((Int8*)out)++ = (Int8)constant;
					codeSize += (sizeof(UInt8)*2);
					break;
				case R_AX:
					*out++ = 0x66;
					*out++ = 0x05;
					*((Int16*)out)++ = (Int16)constant;
					codeSize += (sizeof(UInt8)*2 + sizeof(Int16));
					break;
				case R_EAX:
					*out++ = 0x05;
					*((Int32*)out)++ = (Int32)constant;
					codeSize += (sizeof(UInt8) + sizeof(Int32));
					break;
				case R_ECX:
				case R_EDX:
				case R_EBX:
				case R_ESP:
				case R_EBP:
				case R_ESI:
				case R_EDI:
					if (IS_BETWEEN(constant, -127, 127))
					{
						*out++ = 0x83;
						*out++ = MAKE_MOD_RM_BYTE(3, 0, reg);
						*out++ = (Int8)constant;
						codeSize += sizeof(UInt8)*3;
					}
					else
					{
						*out++ = 0x81;
						*out++ = MAKE_MOD_RM_BYTE(3, 0, reg);
						*((Int32*)out)++ = constant;
						codeSize += (sizeof(UInt8)*2 + sizeof(Int32));
					}
					break;

				case R_CX:
				case R_DX:
				case R_BX:
				case R_SP:
				case R_BP:
				case R_SI:
				case R_DI:
					if (IS_BETWEEN(constant, -127, 127))
					{
						*out++ = 0x66;
						*out++ = 0x83;
						*out++ = MAKE_MOD_RM_BYTE(3, 0, reg - R_AX);
						*out++ = (Int8)constant;
						codeSize += sizeof(UInt8)*4;
					}
					else
					{
						*out++ = 0x66;
						*out++ = 0x81;
						*out++ = MAKE_MOD_RM_BYTE(3, 0, reg - R_AX);
						*((Int16*)out)++ = constant;
						codeSize += (sizeof(UInt8)*3 + sizeof(Int16));
					}
					break;

				case R_CL:
				case R_DL:
				case R_BL:
				case R_AH:
				case R_CH:
				case R_DH:
				case R_BH:
					*out++ = 0x80;
					*out++ = MAKE_MOD_RM_BYTE(3, 0, reg - R_AL);
					*((Int8*)out)++ = (Int8)constant;
					codeSize += (sizeof(UInt8)*2 + sizeof(Int8));
					break;
				}

				(UInt8*)i += (sizeof(struct Instruction) + sizeof(struct AddConstantToRegister));
				break;
			}
		case OT_SUB_CONSTANT_FROM_REGISTER:

			(UInt8*)i += (sizeof(struct Instruction) + sizeof(struct SubConstantFromRegister));
			break;
		case OT_INC_REGISTER:
			{
				const enum REGISTER reg = ((struct IncRegister*)(i + 1))->reg;

				switch (reg)
				{
				case R_EAX:
				case R_ECX:
				case R_EDX:
				case R_EBX:
				case R_ESP:
				case R_EBP:
				case R_ESI:
				case R_EDI:
					*out++ = 0x40 + reg;
					codeSize += sizeof(UInt8);
					break;

				case R_AX:
				case R_CX:
				case R_DX:
				case R_BX:
				case R_SP:
				case R_BP:
				case R_SI:
				case R_DI:
					*out++ = 0x66;
					*out++ = 0x40 + (reg - R_AX);
					codeSize += sizeof(UInt8)*2;
					break;

				case R_AL:
				case R_CL:
				case R_DL:
				case R_BL:
				case R_AH:
				case R_CH:
				case R_DH:
				case R_BH:
					*out++ = 0xFE;
					*out++ = MAKE_MOD_RM_BYTE(3, 0, reg - R_AL);
					codeSize += sizeof(UInt8)*2;
					break;
				}

				(UInt8*)i += (sizeof(struct Instruction) + sizeof(struct IncRegister));
				break;
			}
		case OT_JMP_CONSTANT:
			{
				const Int constant = ((struct JmpConstant*)(i + 1))->constant;
				
				if (IS_BETWEEN(constant, -127, 127))
				{
					*out++ = 0xEB;
					*out++ = (Int8)constant;
					codeSize += sizeof(UInt8)*2;
				}
				else
				{
					*out++ = 0xE9;
					*((Int32*)out)++ = constant;
					codeSize += (sizeof(UInt8) + sizeof(Int32));
				}

				(UInt8*)i += (sizeof(struct Instruction) + sizeof(struct JmpConstant));
				break;
			}
		case OT_JMP_CONSTANT_IF_CONDITION:
			{
				static const UInt8 jmpConditionOpCodes[] = 
				{
					0x77, // JC_JA
					0x73, // JC_JAE
					0x72, // JC_JB
					0x76, // JC_JBE
					0x74, // JC_JE
					0x7F, // JC_JG
					0x7D, // JC_JGE
					0x7C, // JC_JL
					0x7E, // JC_JLE
					0x76, // JC_JNA
					0x72, // JC_JNAE
					0x73, // JC_JNB
					0x77, // JC_JNBE
					0x75, // JC_JNE
					0x7E, // JC_JNG
					0x7C, // JC_JNGE
					0x7D, // JC_JNL
					0x7F, // JC_JNLE
					0x75 // JC_JNZ
				};


				const enum JMP_CONDITION condition = ((struct JmpConstantIfCondition*)(i + 1))->condition;
				const Int constant = ((struct JmpConstantIfCondition*)(i + 1))->constant;

				if (IS_BETWEEN(constant, -127, 127))
				{
					*out++ = jmpConditionOpCodes[condition];
					*((Int8*)out)++ = (Int8)constant;
					codeSize += (sizeof(UInt8) + sizeof(Int8));
				}
				else if (IS_BETWEEN(constant, -32767, 32768))
				{
					*out++ = 0x66;
					*out++ = 0x0F;
					*out++ = jmpConditionOpCodes[condition] + 0x10;
					*((Int16*)out)++ = (Int16)constant;
					codeSize += (sizeof(UInt8)*3 + sizeof(Int16));
				}
				else
				{
					*out++ = 0x0F;
					*out++ = jmpConditionOpCodes[condition] + 0x10;
					*((Int32*)out)++ = constant;
					codeSize += (sizeof(UInt8)*2 + sizeof(Int32));
				}

				(UInt8*)i += (sizeof(struct Instruction) + sizeof(struct JmpConstantIfCondition));
				break;

			}
		case OT_RET:

			*out++ = 0xC3;
			++codeSize;
			goto End;
		}
	}
End:
	return codeSize;
}